import * as lsp from "vscode-languageserver";
import { t } from "./analysis";

const tokenTypes = new Map<string, number>();
const tokenModifiers = new Map<string, number>();

const tokenTypesLegend = [
	'comment',
    'string',
    'keyword',
    'number',
    'regexp',
    'operator',
    'namespace',
	'type',
    'struct',
    'class',
    'interface',
    'enum',
    'enumMember',
    'typeParameter',
    'function',
	'method',
    'decorator',
    'macro',
    'variable',
    'parameter',
    'property',
    'label'
];
tokenTypesLegend.forEach((tokenType, index) => tokenTypes.set(tokenType, index));

const tokenModifiersLegend = [
	'declaration',
    'documentation',
    'readonly',
    'static',
    'abstract',
    'deprecated',
	'modification',
    'async'
];
tokenModifiersLegend.forEach((tokenModifier, index) => tokenModifiers.set(tokenModifier, index));

function encodeTokenType(tokenType: lsp.SemanticTokenTypes): number {
    if (tokenTypes.has(tokenType)) {
        return tokenTypes.get(tokenType)!;
    }
    return 0;
}

function encodeTokenModifiers(modifiers: lsp.SemanticTokenModifiers[]): number {
    let result = 0;
    for (let i = 0; i < modifiers.length; i++) {
        const token = modifiers[i];
        if (tokenModifiers.has(token)) {
            result |= (1 << tokenModifiers.get(token)!);
        }
    }
    return result;
}

const symbolFlagToSemanticTokenType: { [k: number]: lsp.SemanticTokenTypes } = {
    [t.SymbolKind.module]: lsp.SemanticTokenTypes.namespace,
    [t.SymbolKind.class]: lsp.SemanticTokenTypes.class,
    [t.SymbolKind.interface]: lsp.SemanticTokenTypes.interface,
    [t.SymbolKind.enum]: lsp.SemanticTokenTypes.enum,
    [t.SymbolKind.enumMember]: lsp.SemanticTokenTypes.enumMember,
    [t.SymbolKind.property]: lsp.SemanticTokenTypes.property,
    [t.SymbolKind.function]: lsp.SemanticTokenTypes.function,
    [t.SymbolKind.method]: lsp.SemanticTokenTypes.method,
    [t.SymbolKind.parameter]: lsp.SemanticTokenTypes.parameter,
    [t.SymbolKind.variable]: lsp.SemanticTokenTypes.variable,
    [t.SymbolKind.constant]: lsp.SemanticTokenTypes.variable,
    [t.SymbolKind.macroVariable]: lsp.SemanticTokenTypes.macro,
    [t.SymbolKind.macroFunction]: lsp.SemanticTokenTypes.macro,
    [t.SymbolKind.section]: lsp.SemanticTokenTypes.variable,
    [t.SymbolKind.sectionMember]: lsp.SemanticTokenTypes.property,
};

interface SemanticTokenResult {
    pos: number;
    line: number;
    character: number;
    length: number;
    tokenType: number;
    tokenModifiers: number;
}

let tokenCache: SemanticTokenResult[] = [];
let tokenRange: { start: number; end: number } | undefined = undefined;

function afterTokenRangeStart(node: t.ReadonlyTextRange) {
    return !tokenRange || node.start >= tokenRange.start;
}

function withinTokenRange(node: t.ReadonlyTextRange) {
    return !tokenRange || (node.start >= tokenRange.start && node.end <= tokenRange.end);
}

function pushToken(builder: lsp.SemanticTokensBuilder, token: SemanticTokenResult) {
    builder.push(
        token.line,
        token.character,
        token.length,
        token.tokenType,
        token.tokenModifiers
    );
}

function pushTokenCache(builder: lsp.SemanticTokensBuilder) {
    tokenCache.sort((a, b) => a.pos - b.pos);
    for (const token of tokenCache) {
        pushToken(builder, token);
    }
}

function buildSemanticTokenOfNode(identifier: t.Identifier, source: t.SourceFile): void {
    const symbol = identifier.symbol;
    if (!symbol || !withinTokenRange(identifier)) {
        return;
    }
    let modifiers: lsp.SemanticTokenModifiers[] = [];
    // check flag
    if (symbol.isCheckFlag(t.CheckFlag.readonly)) {
        modifiers.push(lsp.SemanticTokenModifiers.readonly);
    }
    if (symbol.is(t.SymbolKind.macro)) {
        modifiers.push(lsp.SemanticTokenModifiers.static);
    }
    // symbol flag
    let tokenTypes = symbolFlagToSemanticTokenType[symbol.kind & (~t.SymbolKind.undefined)];
    if (tokenTypes !== undefined) {
        let startPos = source.getLineAndCharactorOfPosition(identifier.start);
        let length = identifier.getWidth();
        tokenCache.push({
            pos: identifier.start,
            line: startPos.line,
            character: startPos.character,
            length,
            tokenType: encodeTokenType(tokenTypes),
            tokenModifiers: encodeTokenModifiers(modifiers)
        });
    }
}

// 添加SemanticTokens结果时，需要额外注意Node添加顺序，客户端接收的需要是顺序的数组，如果顺序错误，排序错误的位置将会终止。

function pushSingleToken(node: t.Node | undefined, source: t.SourceFile, types: lsp.SemanticTokenTypes, modifiers: lsp.SemanticTokenModifiers[]) {
    if (!node || !withinTokenRange(node)) {
        return;
    }
    let start = source.getLineAndCharactorOfPosition(node.start);
    tokenCache.push({
        pos: node.start,
        line: start.line,
        character: start.character,
        length: node.getWidth(),
        tokenType: encodeTokenType(types),
        tokenModifiers: encodeTokenModifiers(modifiers)});
}

function buildSemanticTokenOfMetadataAreaName(area: t.MDMAreaName, source: t.SourceFile) {
    if (!area.areaName || area.areaName.isMissingNode() || !afterTokenRangeStart(area.areaName)) {
        return;
    }
    pushSingleToken(
        area.areaName,
        source,
        lsp.SemanticTokenTypes.variable,
        [lsp.SemanticTokenModifiers.declaration]
    );
}

function buildSemanticTokenOfMetadataPropertyLike(property: t.MDMKeyValuePair, source: t.SourceFile) {
    if (!property.name || property.name.isMissingNode() || !withinTokenRange(property.name)) {
        return;
    }
    pushSingleToken(
        property.name,
        source,
        lsp.SemanticTokenTypes.property,
        [lsp.SemanticTokenModifiers.definition]
    );
}

function buildSemanticTokenOfMetadataStyleLike(node: t.MDMDeclaration | undefined, source: t.SourceFile) {
    if (!node || !afterTokenRangeStart(node)) {
        return;
    }
    if (node.name && withinTokenRange(node.name)) {
        pushSingleToken(
            node.name,
            source,
            lsp.SemanticTokenTypes.property,
            [lsp.SemanticTokenModifiers.definition]
        );
    }
    node.forEachChild(child => {
        if (child.is<t.MDMProperty>(t.SyntaxKind.mdmProperty) && afterTokenRangeStart(child)) {
            buildSemanticTokenOfMetadataPropertyLike(child, source);
        }
    });
}

function buildSemanticTokenOfMetadataStyle(style: t.MDMStyle | undefined, source: t.SourceFile) {
    if (!style || !style.name || style.name.isMissingNode() || !afterTokenRangeStart(style)) {
        return;
    }
    if (style.name && withinTokenRange(style.name)) {
        pushSingleToken(
            style.name,
            source,
            lsp.SemanticTokenTypes.keyword,
            [lsp.SemanticTokenModifiers.definition]
        );
    }
    style.forEachObject(obj => {
        buildSemanticTokenOfMetadataAreaObject(obj, source);
        buildSemanticTokenOfMetadataStyle(obj, source);
    });
    buildSemanticTokenOfMetadataStyleLike(style, source);
    buildSemanticTokenOfMetadataStyleLike(style.font, source);
    buildSemanticTokenOfMetadataStyleLike(style.control, source);
    buildSemanticTokenOfMetadataStyleLike(style.cell, source);
}

function buildSemanticTokenOfMetadataAreaObject(node: t.MDMArealBase, source: t.SourceFile) {
    if (afterTokenRangeStart(node) && node.areaName && withinTokenRange(node.areaName)) {
        pushSingleToken(
            node.areaName,
            source,
            lsp.SemanticTokenTypes.decorator,
            [lsp.SemanticTokenModifiers.declaration]
        );
    }
}

function buildSemanticTokenOfMetadataLabeledObject(node: t.MDMLabeledObject, source: t.SourceFile) {
    if (node.properties && afterTokenRangeStart(node.properties)) {
        node.properties.forEachObject(obj => {
            buildSemanticTokenOfMetadataAreaObject(obj, source);
            obj.forEachItem(property => {
                buildSemanticTokenOfMetadataPropertyLike(property, source);
            });
        });
    }
    if (node.templates && afterTokenRangeStart(node.templates)) {
        node.templates.forEachObject(obj => {
            buildSemanticTokenOfMetadataAreaObject(obj, source);
            obj.forEachItem(template => {
                buildSemanticTokenOfMetadataPropertyLike(template, source);
            });
        });
    }
    buildSemanticTokenOfMetadataStyle(node.style, source);
    buildSemanticTokenOfMetadataStyle(node.labelStyle, source);
}

function buildSemanticTokenOfKeyword(token: t.Token<t.SyntaxKind> | undefined, source: t.SourceFile) {
    if (!token || token.isMissingNode() || !withinTokenRange(token)) {
        return;
    }
    pushSingleToken(
        token,
        source,
        lsp.SemanticTokenTypes.keyword,
        []
    );
}

function buildSemanticTokenOfClass(node: t.MDMClass, source: t.SourceFile) {
    if (!afterTokenRangeStart(node)) {
        return;
    }
    let fieldsKw = node.name;
    if (!fieldsKw.isMissingNode()) {
        buildSemanticTokenOfKeyword(fieldsKw, source);
    }
    buildSemanticTokenOfMetadataLabeledObject(node, source);
    node.types.forEach(type => buildSemanticTokenOfMetadataField(type, source));
    node.fields.forEach(childFields => buildSemanticTokenOfMetadataField(childFields, source));
    node.pages.forEach(page => buildSemanticTokenOfMetadataField(page, source));
}

function buildSemanticTokenOfMetadataCategoryOtherLike(node: (t.MDMDeclaration & { field?: t.StringLiteral | t.MDMField }) | undefined, source: t.SourceFile) {
    if (!node || !node.name || node.isMissingNode() || node.name.isMissingNode() || !afterTokenRangeStart(node)) {
        return;
    }
    pushSingleToken(
        node.name,
        source,
        lsp.SemanticTokenTypes.decorator,
        []
    );
    if (node.field && node.field.isMDMField() && afterTokenRangeStart(node.field)) {
        buildSemanticTokenOfMetadataField(node.field, source);
    }
}

function buildSemanticTokenOfMetadataCategory(node: t.MDMCategory, source: t.SourceFile) {
    if (!node.name || node.name.isMissingNode() || !afterTokenRangeStart(node)) {
        return;
    }
    pushSingleToken(
        node.listName,
        source,
        lsp.SemanticTokenTypes.enumMember,
        [lsp.SemanticTokenModifiers.static, lsp.SemanticTokenModifiers.declaration]
    );
    pushSingleToken(
        node.list,
        source,
        lsp.SemanticTokenTypes.variable,
        [lsp.SemanticTokenModifiers.static]
    );
    if ((node.list && !node.listName) || !node.list) {
        pushSingleToken(
            node.name,
            source,
            lsp.SemanticTokenTypes.enumMember,
            [lsp.SemanticTokenModifiers.static, lsp.SemanticTokenModifiers.declaration]
        );
    }
    buildSemanticTokenOfMetadataAreaObject(node, source);
    if (node.sublist) {
        buildSemanticTokenOfKeyword(node.sublist.name, source);
        buildSemanticTokenOfMetadataLabeledObject(node.sublist, source);
    }
    // 后缀
    pushSingleToken(node.na, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.dk, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.ref, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.exclusive, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.factor, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.keycode, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.canfilter, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.nofilter, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.expression?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.elementType?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.namespace, source, lsp.SemanticTokenTypes.decorator, []);
    buildSemanticTokenOfMetadataCategoryOtherLike(node.other, source);
    buildSemanticTokenOfMetadataCategoryOtherLike(node.multiplier, source);
    if (node.elementType && node.elementType.elementType.isIdentifier()) {
        pushSingleToken(
            node.elementType.elementType,
            source,
            lsp.SemanticTokenTypes.type,
            []
        );
    }
    if (node.expression) {
        pushSingleToken(node.expression.noDeriveElements, source, lsp.SemanticTokenTypes.keyword, []);
        pushSingleToken(node.expression.deriveElements, source, lsp.SemanticTokenTypes.keyword, []);
    }
    if (node.categories) {
        node.categories.forEachElement(cat => buildSemanticTokenOfMetadataCategory(cat, source));
    }
    pushSingleToken(node.randomize, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.rotate, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.reverse, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.ascending, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.descending, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(node.fix, source, lsp.SemanticTokenTypes.decorator, []);
}

function buildSemanticTokenOfMetadataField(field: t.MDMField, source: t.SourceFile) {
    let name = field.name;
    if (name.isMissingNode() || !afterTokenRangeStart(field)) {
        return;
    }
    pushSingleToken(
        name,
        source,
        lsp.SemanticTokenTypes.variable,
        [lsp.SemanticTokenModifiers.static, lsp.SemanticTokenModifiers.declaration]
    );
    buildSemanticTokenOfMetadataLabeledObject(field, source);
    pushSingleToken(
        field.fieldFlagToken,
        source,
        lsp.SemanticTokenTypes.type,
        [lsp.SemanticTokenModifiers.modification]
    );
    field.categories.forEachElement(cat => buildSemanticTokenOfMetadataCategory(cat, source));
    if (field.class) {
        buildSemanticTokenOfClass(field.class, source);
    }

    pushSingleToken(field.axis?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(field.precision?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(field.codes?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(field.validation?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(field.expression?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(field.defaultAnswer?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(field.initialAnswer?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(field.usageType?.name, source, lsp.SemanticTokenTypes.decorator, []);
    pushSingleToken(field.helperFields?.name, source, lsp.SemanticTokenTypes.decorator, []);

    buildSemanticTokenOfKeyword(field.noCaseData, source);
    buildSemanticTokenOfKeyword(field.unversioned, source);
    buildSemanticTokenOfKeyword(field.expand, source);
    buildSemanticTokenOfKeyword(field.row, source);
    buildSemanticTokenOfKeyword(field.column, source);
    buildSemanticTokenOfKeyword(field.grid, source);
}

function buildSemanticTokenOfMetadataDocument(document: t.MDMDocument | undefined, source: t.SourceFile) {
    if (!document || !afterTokenRangeStart(document)) {
        return;
    }
    // 客户端接收的SemanticTokens结果是顺序数组，因此此处只能顺序遍历子节点
    document.forEachChild(child => {
        if (child.is<t.MDMHierarchicalView>(t.SyntaxKind.mdmHierarchicalView)) {
            buildSemanticTokenOfMetadataLabeledObject(child, source);
        }
        else if (child.is<t.MDMAreaName>(t.SyntaxKind.mdmAreaName)) {
            buildSemanticTokenOfMetadataAreaName(child, source);
        }
        else if (child.isMDMField()) {
            buildSemanticTokenOfMetadataField(child, source);
        }
        else if (child.isPreprocessor()) {
            child.forEachChild(node => {
                if (node.isIdentifier()) {
                    buildSemanticTokenOfNode(node, source);
                }
            });
        }
    });
}

export function provideDocumentSemanticTokens(source: t.SourceFile, range?: { start: number, end: number }) {
    tokenCache = [];
    tokenRange = range;
    const builder = new lsp.SemanticTokensBuilder();
    if (source.metadata) {
        buildSemanticTokenOfMetadataDocument(source.metadata, source);
    }
    else {
        for (const identifier of source.identifiers) {
            buildSemanticTokenOfNode(identifier, source);
        }
        let metadataSection: t.MetadataSection | undefined;
        for (const section of source.sections) {
            if (section.is<t.MetadataSection>(t.SyntaxKind.metadataSection)) {
                metadataSection = section;
                break;
            }
        }
        buildSemanticTokenOfMetadataDocument(metadataSection?.document, source);
    }
    pushTokenCache(builder);
    tokenCache = [];
    return builder.build();
}
